using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Globalization;
using ResourceBlender_Express.Validators;
using System.ComponentModel;
using System.Data.Common;
using System.Data;
using System.Windows.Controls.Primitives;

namespace ResourceBlender_Express {
    /// <summary>
    /// A window for adding or editing an element and the resource it uses.
    /// </summary>
    public partial class WinAddElement : Window {
        public delegate void ResourcesChangedDelegate();
        /// <summary>
        /// Occurs when the resource list changes (allowing a parent window to refresh).
        /// </summary>
        public event ResourcesChangedDelegate ResourcesChanged;

        private bool editing = false;
        private RBElement element;
        private ObservableSortedDictionary<string, string> bundles,
                                                           languages;
        private string defaultLanguage = string.Empty,
                       crossTabColumns;

        /// <summary>
        /// Gets or sets the element being edited.
        /// </summary>
        /// <value>The element.</value>
        public RBElement Element {
            get { return element; }
            set {
                element = value;
                DataContext = element;
            }
        }

        /// <summary>
        /// Initializes a new <see cref="WinAddElement"/>.
        /// </summary>
        /// <param name="bundles">The available bundles.</param>
        /// <param name="resourceCache">A Dictionary of resource IDs and their translations in the default language for the application.</param>
        /// <param name="crossTabColumns">The cross tab column query which can be used to load translations for a selected resource.</param>
        public WinAddElement(ObservableSortedDictionary<string, string> bundles, SortedDictionary<int, string> resourceCache, string crossTabColumns, ObservableSortedDictionary<string, string> languages) {
            this.bundles = bundles;
            this.languages = languages;
            this.crossTabColumns = crossTabColumns;
            InitializeComponent();
            cboBundle.ItemsSource = bundles;
            cboResource.ItemsSource = resourceCache;
        }

        /// <summary>
        /// Initializes a new <see cref="WinAddElement"/>.
        /// </summary>
        /// <param name="bundles">The available bundles.</param>
        /// <param name="resourceCache">A Dictionary of resource IDs and their translations in the default language for the application.</param>
        /// <param name="crossTabColumns">The cross tab column query which can be used to load translations for a selected resource.</param>
        /// <param name="element">The element to edit.</param>
        /// <param name="defaultLanguage">The default language for the element's application.</param>
        public WinAddElement(ObservableSortedDictionary<string, string> bundles, SortedDictionary<int, string> resourceCache, string crossTabColumns, ObservableSortedDictionary<string, string> languages, RBElement element, string defaultLanguage)
            : this(bundles, resourceCache, crossTabColumns, languages) {
            if(element.ID >= 0) {
                editing = true;
            }
            this.Element = element;
            this.defaultLanguage = defaultLanguage;
            Binding bndDefaultLanguage = new Binding(string.Format("Resource.Translations[{0}]", defaultLanguage));
            bndDefaultLanguage.Mode = BindingMode.TwoWay;
            bndDefaultLanguage.Source = Element;
            defaultLanguageInput.SetBinding(TranslationInput.TranslationProperty, bndDefaultLanguage);
            defaultLanguageInput.AddBindingRule(new StringValidationRule(0, -1, "The default language must be entered"));
            defaultLanguageInput.Expand();
            PopulateLanguages();
        }

        /// <summary>
        /// Sets the text on the status message (null hides the statusbar and label).
        /// </summary>
        /// <param name="message">The message.</param>
        private void SetStatus(string message) {
            if(message == null) {
                prgStatus.Visibility = Visibility.Hidden;
                lblStatus.Visibility = Visibility.Hidden;
            } else {
                prgStatus.Visibility = Visibility.Visible;
                lblStatus.Visibility = Visibility.Visible;
                lblStatus.Content = message;
            }
        }

        /// <summary>
        /// Populates the textboxes for each language used by the application.
        /// </summary>
        private void PopulateLanguages() {
            stkTranslations.Children.RemoveRange(0, stkTranslations.Children.Count);
            IDictionary<string, RBTranslation> translations = Element.Resource.Translations;
            foreach(KeyValuePair<string, RBTranslation> translation in translations) { // <code, translation>
                if(translation.Key == defaultLanguage) {
                    continue;
                }
                Binding bndTranslation = new Binding(string.Format("Resource.Translations[{0}]", translation.Key));
                bndTranslation.Mode = BindingMode.TwoWay;
                bndTranslation.Source = Element;
                TranslationInput translationInput = new TranslationInput();//translation.Value);
                translationInput.SetBinding(TranslationInput.TranslationProperty, bndTranslation);
                translationInput.Margin = new Thickness(0, 2, 0, 2);
                stkTranslations.Children.Add(translationInput);
            }
            LoadTranslatorComments();
        }

        /// <summary>
        /// Loads the translator comments for the selected element.
        /// </summary>
        private void LoadTranslatorComments() {
            BackgroundWorker worker = new BackgroundWorker();
            SetStatus("Loading translator comments...");
            worker.DoWork += (s, dwe) => {
                Database db = null;
                try {
                    db = new Database();
                    db.OpenConnection();
                    using(Database.Command cmd = db.CreateCommand()) {
                        cmd.CommandText = string.Format("SELECT language, translatorcomment FROM {0}resources WHERE resourceid = {1}{2}", db.TablePrefix, Element.Resource.ID, db.GenerateInClause("language", languages.Keys.ToArray(), true));
                        DbDataReader reader = cmd.ExecuteReader();
                        while(reader.Read()) {
                            Element.Resource.Translations[reader["language"].ToString()].Comment = (reader["translatorcomment"] ?? string.Empty).ToString();
                        }
                        reader.Close();
                    }
                } catch(Exception ex) {
                    dwe.Result = ex.Message;
                } finally {
                    if(db != null) {
                        try {
                            db.CloseConnection();
                        } catch { }
                    }
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                SetStatus(null);
                if(rwe.Result != null) {
                    DialogBox.Show(rwe.Result.ToString(), "Error Loading Translator Comments", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                }
            };
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Loads a new resource when the 'Resource ID' combobox changes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void cboBundle_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if(cboResource.SelectedValue == null) {
                return;
            }
            BackgroundWorker worker = new BackgroundWorker();
            WinProgress progress = new WinProgress("Loading resource...", true);
            worker.DoWork += (s, dwe) => {
                Database db = null;
                try {
                    db = new Database();
                    db.OpenConnection();
                    using(Database.Command cmd = db.CreateCommand()) {
                        int resourceID = int.Parse(dwe.Argument.ToString());
                        cmd.CommandText = string.Format("SELECT {0} FROM {1}resources WHERE resourceid = @tID", crossTabColumns, db.TablePrefix);
                        cmd.AddParameterWithValue("@tID", resourceID);
                        DbDataReader reader = cmd.ExecuteReader();
                        RBResource newResource = new RBResource(resourceID, string.Empty);
                        if(reader.Read()) {
                            for(int i = 0; i < reader.FieldCount; i++) {
                                string languageCode = reader.GetName(i);
                                newResource.Translations.Add(languageCode, new RBTranslation(languageCode, (reader[i] ?? string.Empty).ToString(), ""));
                            }
                        }
                        reader.Close();

                        // fill the translators' comment boxes by changing the field name in the query
                        cmd.CommandText = cmd.CommandText.Replace("translation", "translatorcomment");
                        reader = cmd.ExecuteReader();
                        if(reader.Read()) {
                            for(int i = 0; i < reader.FieldCount; i++) {
                                newResource.Translations[reader.GetName(i)].Comment = (reader[i] ?? string.Empty).ToString();
                            }
                        }
                        reader.Close();

                        // fill the overall resource comment
                        cmd.CommandText = "SELECT comment FROM " + db.TablePrefix + "resourcecomments WHERE resourceid = @tID";
                        newResource.Comment = (cmd.ExecuteScalar() ?? string.Empty).ToString();

                        Element.Resource = newResource;
                        dwe.Result = Element;
                    }
                } catch(Exception ex) {
                    dwe.Result = "Error changing displayed resource: " + ex.Message;
                } finally {
                    if(db != null) {
                        try {
                            db.CloseConnection();
                        } catch { }
                    }
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                progress.Close();
                if(!(rwe.Result is RBElement)) {
                    DialogBox.Show(rwe.Result.ToString(), "Database Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                }
            };
            progress.Show();
            worker.RunWorkerAsync(cboResource.SelectedValue);
        }

        /// <summary>
        /// Validates the user input, saves/updates the element and it's resource then closes the dialog.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Save(object sender, ExecutedRoutedEventArgs e) {
            Console.WriteLine(1);
            txtElement.GetBindingExpression(TextBox.TextProperty).UpdateSource();
            if(!defaultLanguageInput.ValidateInput()) {
                return;
            } else if(string.IsNullOrEmpty(txtElement.Text)) {
                DialogBox.Show(this, "Element name must be entered", "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                txtElement.Focus();
            } else if(string.IsNullOrEmpty(cboBundle.SelectedValue as string)) {
                DialogBox.Show(this, "A bundle must be selected", "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                cboBundle.Focus();
            } else {
                BackgroundWorker worker = new BackgroundWorker();
                WinProgress progress = new WinProgress("Saving element...", true);
                worker.DoWork += (s, dwe) => {
                    Database db = null;
                    List<string> errors = new List<string>();
                    try {
                        db = new Database();
                        db.OpenConnection();
                        using(Database.Command cmd = db.CreateCommand()) {
                            object[] selectedValues = (object[])dwe.Argument;
                            int resourceID = (int)selectedValues[0];
                            string bundleID = selectedValues[1].ToString();
                            if(resourceID == -1) {
                                cmd.CommandText = "SELECT MAX(resourceid) FROM " + db.TablePrefix + "resources";
                                int maxId = 0;
                                using(DbDataReader maxIdReader = cmd.ExecuteReader(CommandBehavior.SingleRow)) {
                                    if(maxIdReader.HasRows) {
                                        maxIdReader.Read();
                                        int.TryParse(maxIdReader[0].ToString(), out maxId);
                                        maxId++;
                                    }
                                    maxIdReader.Close();
                                }
                                resourceID = maxId;
                            }

                            cmd.Parameters.Clear();
                            switch(db.InstanceType) {
                                case Database.DatabaseType.MySQL:
                                    cmd.CommandText = string.Format("INSERT INTO {0}resources (resourceid, language, translation, translatorcomment) VALUES (@tID, @lang, @trans, @comment) ON DUPLICATE KEY UPDATE translation = @trans, translatorcomment = @comment", db.TablePrefix);
                                    break;
                                case Database.DatabaseType.SQLServer:
                                    cmd.CommandText = string.Format("UPDATE {0}resources SET translation = @trans, translatorcomment = @comment WHERE resourceid = @tID AND language = @lang; IF @@ROWCOUNT = 0 INSERT INTO {0}resources (resourceid, language, translation, translatorcomment) VALUES (@tID, @lang, @trans, @comment)", db.TablePrefix);
                                    break;
                                case Database.DatabaseType.SQLite:
                                    cmd.CommandText = string.Format("INSERT OR REPLACE INTO {0}resources (resourceid, language, translation, translatorcomment) VALUES (@tID, @lang, @trans, @comment)", db.TablePrefix);
                                    break;
                            }
                            cmd.AddParameterWithValue("@tID", resourceID);
                            cmd.AddParameterWithValue("@trans", "");
                            cmd.AddParameterWithValue("@lang", "");
                            cmd.AddParameterWithValue("@comment", "");
                            RBResource resource = Element.Resource;
                            foreach(KeyValuePair<string, RBTranslation> translation in resource.Translations) { // <code, translation>
                                if(!string.IsNullOrEmpty(translation.Value.Translation) || !string.IsNullOrEmpty(translation.Value.Comment)) {
                                    cmd.Parameters["@trans"].Value = translation.Value.Translation;
                                    cmd.Parameters["@lang"].Value = translation.Key;
                                    if(string.IsNullOrEmpty(translation.Value.Comment)) {
                                        cmd.Parameters["@comment"].Value = DBNull.Value;
                                    } else {
                                        cmd.Parameters["@comment"].Value = translation.Value.Comment;
                                    }
                                    try {
                                        cmd.ExecuteNonQuery();
                                    } catch(Exception ex) {
                                        errors.Add(string.Format("Error updating translation for {0}: {1}", translation.Key, ex.Message));
                                    }
                                }
                            }
                            if(!string.IsNullOrEmpty(Element.Resource.Comment)) {
                                cmd.Parameters.Clear();
                                switch(db.InstanceType) {
                                    case Database.DatabaseType.MySQL:
                                        cmd.CommandText = string.Format("INSERT INTO {0}resourcecomments (resourceid, comment) VALUES (@tID, @comment) ON DUPLICATE KEY UPDATE comment = @comment", db.TablePrefix);
                                        break;
                                    case Database.DatabaseType.SQLServer:
                                        cmd.CommandText = string.Format("UPDATE {0}resourcecomments SET comment = @comment WHERE resourceid = @tID; IF @@ROWCOUNT = 0 INSERT INTO {0}resourcecomments (resourceid, comment) VALUES (@tID, @comment)", db.TablePrefix);
                                        break;
                                    case Database.DatabaseType.SQLite:
                                        cmd.CommandText = string.Format("INSERT OR REPLACE INTO {0}resourcecomments (resourceid, comment) VALUES (@tID, @comment)", db.TablePrefix);
                                        break;
                                }
                                cmd.AddParameterWithValue("@tID", resourceID);
                                cmd.AddParameterWithValue("@comment", Element.Resource.Comment);
                                cmd.ExecuteNonQuery();
                            }
                            cmd.Parameters.Clear();
                            if(editing) {
                                cmd.CommandText = "UPDATE " + db.TablePrefix + "elements SET bundle = @bundle, elementname = @element, resourceid = @rID, comment = @comment WHERE id = @id";
                                cmd.AddParameterWithValue("@id", Element.ID);
                            } else {
                                cmd.CommandText = "INSERT INTO " + db.TablePrefix + "elements (applicationid, bundle, elementname, resourceid, comment) VALUES (@appId, @bundle, @element, @rID, @comment)";
                                cmd.AddParameterWithValue("@appId", Element.ApplicationID);
                            }
                            cmd.AddParameterWithValue("@rID", resourceID);
                            cmd.AddParameterWithValue("@bundle", bundleID);
                            cmd.AddParameterWithValue("@element", Element.Name);
                            cmd.AddParameterWithValue("@comment", Element.Comment);
                            try {
                                cmd.ExecuteNonQuery();
                            } catch(Exception ex) {
                                errors.Add("Error saving element: " + ex.Message);
                            }
                        }
                    } catch(Exception ex) {
                        errors.Add("Fatal error: " + ex.Message);
                    } finally {
                        if(db != null) {
                            try {
                                db.CloseConnection();
                            } catch { }
                        }
                    }
                    if(errors.Count > 0) {
                        dwe.Result = Utils.Implode(errors, Environment.NewLine, false);
                    }
                };
                worker.RunWorkerCompleted += (s, rwe) => {
                    progress.Close();
                    if(rwe.Result == null) {
                        if(ResourcesChanged != null) {
                            ResourcesChanged(); // notify the parent grid so it can refresh
                        }
                        Close();
                    } else {
                        DialogBox.Show(rwe.Result.ToString(), "Database Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                    }
                };
                progress.Show();
                worker.RunWorkerAsync(new object[] { cboResource.SelectedValue, cboBundle.SelectedValue });
            }
        }

        /// <summary>
        /// Closes the dialog without saving.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            Close();
        }

        /// <summary>
        /// Expands all translations.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ExpandAll(object sender, ExecutedRoutedEventArgs e) {
            foreach(UIElement element in stkTranslations.Children) {
                if(element is TranslationInput) {
                    (element as TranslationInput).Expand();
                }
            }
        }

        /// <summary>
        /// Shows the larger editor.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowFullEditor(object sender, ExecutedRoutedEventArgs e) {
            TextBox source = e.OriginalSource as TextBox;
            if(source == null) {
                return;
            }
            WinFullEditor editor = new WinFullEditor("Editor", source.Text);
            editor.ShowDialog();
            if(editor.DialogResult == true) {
                source.Text = editor.Text;
            }
        }

        /// <summary>
        /// Shows a dialog to find resources which use a similar translation to the user's input
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void FindSimilar(object sender, ExecutedRoutedEventArgs e) {
            TextBox txtSource = e.OriginalSource as TextBox;
            WinFindResource dlg = new WinFindResource(new RBTranslation(e.Parameter.ToString(), txtSource.Text, ""), element.ApplicationID);
            dlg.ShowDialog();
            if(dlg.DialogResult == true) {
                if(dlg.SelectedResource != null) {
                    //cboResource.SetValue(ComboBox.SelectedValueProperty, dlg.SelectedResource);
                    //cboResource.SelectedIndex = 2;
                    cboResource.SelectedValue = dlg.SelectedResource;
                    cboBundle_SelectionChanged(this, null);
                }
            }
        }
    }
}